/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <arch/gic_v3.h>
#include <stdlib/assert.h>
#include <core/dprintf.h>
#include <tools/macros.h>
#include <arch/cpu.h>
#include <arch/scr.h>

#define REG32(addr) (*((volatile uint32_t *)(addr)))
#define REG64(addr) (*((volatile uint64_t *)(addr)))

typedef struct {
    uint64_t dist_base;
    uint64_t rdist_base[CONFIG_SMP_CORES];
} gicv3_t;

static gicv3_t gic;


/*******************************************************************************
 *  Redistributor register 
 ******************************************************************************/
#define GIC_RSGI_OFFSET            0x10000

#define GICR_CTLR(base)            REG32((base) + GIC_RSGI_OFFSET + 0x0U)
#define GICR_IIDR(base)            REG32((base) + GIC_RSGI_OFFSET + 0x4U)
#define GICR_TYPE(base)            REG32((base) + GIC_RSGI_OFFSET + 0x8U)
#define GICR_STATUSR(base)         REG32((base) + GIC_RSGI_OFFSET + 0x10U)
#define GICR_WAKER(base)           REG32((base) + GIC_RSGI_OFFSET + 0x14U)
#define GICR_IGROUPR0(base, n)     REG32((base) + GIC_RSGI_OFFSET + 0x80U + (n) * 4U)  
#define GICR_ISENABLER0(base)      REG32((base) + GIC_RSGI_OFFSET + 0x100U)
#define GICR_ICENABLER0(base)      REG32((base) + GIC_RSGI_OFFSET + 0x180U)
#define GICR_ISPENDR0(base)        REG32((base) + GIC_RSGI_OFFSET + 0x200U)
#define GICR_ICPENDR0(base)        REG32((base) + GIC_RSGI_OFFSET + 0x280U)
#define GICR_ISACTIVER0(base)      REG32((base) + GIC_RSGI_OFFSET + 0x300U)
#define GICR_ICACTIVER0(base)      REG32((base) + GIC_RSGI_OFFSET+ 0x380U)
#define GICR_IPRIORITYR(base, n)   REG32((base) + GIC_RSGI_OFFSET + 0x400U + ((n)/4U) * 4U)
#define GICR_ICFGR0(base)          REG32((base) + GIC_RSGI_OFFSET + 0xc00U)
#define GICR_ICFGR1(base)          REG32((base) + GIC_RSGI_OFFSET + 0xc04U)
#define GICR_IGRPMODR0(base, n)    REG32((base) + GIC_RSGI_OFFSET + 0xd00U + (n) * 4U)

/*******************************************************************************
 * Distributor register
 ******************************************************************************/
#define GICD_CTLR(base)          REG32((base) + 0x0U)
#define GICD_TYPER(base)         REG32((base) + 0x4U)
#define GICD_IIDR(base)          REG32((base) + 0x8U)
#define GICD_IGROUPR(base, n)    REG32((base) + 0x80U + ((n) / 32U) * 4U)
#define GICD_ISENABLER(base, n)  REG32((base) + 0x100U + ((n) / 32U) * 4U)
#define GICD_ICENABLER(base, n)  REG32((base) + 0x180U + ((n) / 32U) * 4U)
#define GICD_ISPENDR(base, n)    REG32((base) + 0x200U + ((n) / 32U) * 4U)
#define GICD_ICPENDR(base, n)    REG32((base) + 0x280U + ((n) / 32U) * 4U)
#define GICD_ISACTIVER(base, n)  REG32((base) + 0x300U + ((n) / 32U) * 4U)
#define GICD_ICACTIVER(base, n)  REG32((base) + 0x380U + ((n) / 32U) * 4U)
#define GICD_IPRIORITYR(base, n) REG32((base) + 0x400U + ((n) / 4U) * 4U)
#define GICD_ITARGETSR(base, n)  REG32((base) + 0x800U + ((n) / 4U) * 4U)
#define GICD_ICFGR(base, n)      REG32((base) + 0xc00U + ((n) / 16U) * 4U)
#define GICD_NSACR(base, n)      REG32((base) + 0xe00U + ((n) / 16U) * 4U)
#define GICD_SGIR(base, n)       REG32((base) + 0xf00U)
#define GICD_CPENDSGIR(base, n)  REG32((base) + 0xf10U + ((n) / 4U) * 4U)
#define GICD_SPENDSGIR(base, n)  REG32((base) + 0xf20U + ((n) / 4U) * 4U)
#define GICD_IROUTER(base, n)    REG64((base) + 0x8000U + ((n) * 8U))

void gic_dist_disable(void)
{
    GICD_CTLR(gic.dist_base) = 0x0U;
}

void gic_dist_enable(void)
{
    GICD_CTLR(gic.dist_base) = GICD_CTRL_ENABLEGRP1_BIT | GICD_CTLR_ARE_BIT;
}

uint32_t gic_get_irqnum(void)
{
    return ((GICD_TYPER(gic.dist_base) & 0x1fU) + 1U) * 32U;
}

uint32_t gic_read_typer(void)
{
    return GICD_TYPER(gic.dist_base);
}

uint32_t gic_read_iidr(void)
{
    return GICD_IIDR(gic.dist_base);
}

void gic_enable_irq(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);

    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        GICR_ISENABLER0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ISENABLER(gic.dist_base, irq) = mask;
    }
}

void gic_disable_irq(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);

    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        GICR_ICENABLER0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ICENABLER(gic.dist_base, irq) = mask;
    }
}

void gic_clear_pend(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);

    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        GICR_ICPENDR0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ICPENDR(gic.dist_base, irq) = mask;
    }
}

void gic_set_pend(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);

    if (irq < 32U){
        uint32_t cpu = current_cpu();
        GICR_ISPENDR0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ISPENDR(gic.dist_base, irq) = mask;
    }
}

void gic_set_active(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);
    
    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        GICR_ISACTIVER0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ISACTIVER(gic.dist_base, irq) = mask;
    }
}

void gic_clear_active(uint32_t irq)
{
    uint32_t mask = 1U << (irq % 32U);

    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        GICR_ICACTIVER0(gic.rdist_base[cpu]) = mask;
    } else {
        GICD_ICACTIVER(gic.dist_base, irq) = mask;
    }
}

void gic_set_priority(uint32_t irq, uint32_t pri)
{
    uint32_t reg_val;

    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        reg_val = GICR_IPRIORITYR(gic.rdist_base[cpu], irq);
        reg_val &= ~(0xffU << ((irq % 4U) * 8U));
        reg_val |= (pri & 0xffU) << ((irq % 4U) * 8U);
        GICR_IPRIORITYR(gic.rdist_base[cpu], irq) = reg_val;
    } else {
        reg_val = GICD_IPRIORITYR(gic.dist_base, irq);
        reg_val &= ~(0xffU << ((irq % 4U) * 8U));
        reg_val |= (pri & 0xffU) << ((irq % 4U) * 8U);
        GICD_IPRIORITYR(gic.dist_base, irq) = reg_val;
    }
}

uint32_t gic_get_priority(uint64_t irq)
{
    if (irq < 32U) {
        uint32_t cpu = current_cpu();
        return (GICR_IPRIORITYR(gic.rdist_base[cpu], irq) >> ((irq % 4U) * 8U)) & 0xffU;
    } else {
        return (GICD_IPRIORITYR(gic.dist_base, irq) >> ((irq % 4U) * 8U)) & 0xffU;
    }
}

uint32_t gic_get_targets(uint32_t irq)
{
    return GICD_IROUTER(gic.dist_base, irq);
}


void gic_set_targets(uint32_t irq, uint32_t target) 
{
    GICD_IROUTER(gic.dist_base, irq) = target & 0xff00ffffffULL;
}

void gic_set_cfg(uint32_t irq, uint32_t cfg)
{
    uint32_t reg_val;

    if (irq < 32) {
        // 0 - 15 SGIs are always edge-triggered
        if (irq >= 16) {
            uint32_t cpu = current_cpu();
            reg_val = GICR_ICFGR1(gic.rdist_base[cpu]);
            reg_val &= ~(3U << ((irq % 16U) * 2U));
            reg_val |= ((cfg & 0x3U) << (((irq % 16U) * 2U) + 1U));

            GICR_ICFGR1(gic.dist_base) = reg_val;
        }
    } else {
        reg_val = GICD_ICFGR(gic.dist_base, irq);
        reg_val &= ~(3U << ((irq % 16U) * 2U));
        reg_val |= ((cfg & 0x3U) << (((irq % 16U) * 2U) + 1U));

        GICD_ICFGR(gic.dist_base, irq) = reg_val;
    }
}

uint32_t gic_get_cfg(uint32_t irq)
{
    if (irq < 32) {
        if (irq >= 16){
            uint32_t cpu = current_cpu();
            return GICR_ICFGR1(gic.rdist_base[cpu]) >> ((irq % 16U) >> 1U);
        }else {
            // SGIs are always edge-triggered
            return 1;
        }
    } else {
        return GICD_ICFGR(gic.dist_base, irq) >> ((irq % 16U) >> 1U);
    }
}

//todo need to test
void gic_send_sgi(uint32_t irq, uint32_t filter, uint8_t target_cpu)
{
    if (filter == 0U){
        uint64_t mpidr = target_cpu;
        uint64_t reg_val = ((mpidr >> 16U) & 0xffU) << ICC_SGI1R_EL1_AFF2_OFF |
                           ((mpidr >> 8U) & 0xffU) << ICC_SGI1R_EL1_AFF1_OFF |
                            irq << ICC_SGI1R_EL1_INTID_OFF;
                            (mpidr & 0xffU);
        scr_write_ICC_SGI1R_EL1(reg_val);
    }else {
        scr_write_ICC_SGI1R_EL1((0x1UL << ICC_SGI1R_EL1_IRM_OFF) | 
                          ((irq & 0xfU) << ICC_SGI1R_EL1_INTID_OFF));
    }
}

uint32_t gic_get_active_irq(void)
{
    return scr_read_ICC_IAR1_EL1() & 0xffffffU;
}

void gic_set_eoi(uint32_t irq)
{
    scr_write_ICC_EOIR1_EL1(irq);
}

void gic_dist_init(uint64_t base)
{
    uint32_t i       = 0U;
    uint32_t max_irq = 0;

    dprintf( "GICD Init Start\n");
    gic.dist_base = base;

    max_irq = gic_get_irqnum();
    dprintf( "gic max irq number %d\n", max_irq);

    if (max_irq > CONFIG_IRQ_COUNT) {
        max_irq = CONFIG_IRQ_COUNT;
    }

    gic_dist_disable();

    /* interrupt is level-sensitive */
    for (i = 32U; i < max_irq; i += 16U) {
        GICD_ICFGR(base, i) = 0U;
    }

    /* Set target cpus */
    uint64_t aff = scr_read_MPIDR();
    aff &= 0xff00ffffffULL;
    for (i = 32U; i < max_irq; i++) {
        GICD_IROUTER(base, i) = aff;
    }

    /* disable all interrupts and clear pending */
    for (i = 0U; i < max_irq; i += 32U) {
        GICD_ICENABLER(base, i) = 0xffffffffU;
        GICD_ICPENDR(base, i)   = 0xffffffffU;
    }

    /* set intertupt priority */
    for (i = 32U; i < max_irq; i += 4U) {
        GICD_IPRIORITYR(base, i) = 0x20202020U;    // priority 32
    }

    gic_dist_enable();
    dprintf( "GICD Init End\n");
}

void gic_rdist_init(uint64_t base)
{
    int i;
    uint64_t rdist_base;
    uint32_t cpu = current_cpu();
    dprintf("GICR Init Start\n");
    rdist_base = base + ((2 << 16) * cpu);

    gic.rdist_base[cpu] = rdist_base;

    GICR_ICENABLER0(rdist_base) = 0xffffffffU;

    GICR_ICPENDR0(rdist_base) = 0xffffffffU;

    // SGIS are always edge-triggered
    // PPI default level-sensitive
    GICR_ICFGR1(rdist_base) = 0x0U;

    for (i = 0U; i < 32U; i += 4U) {
        GICR_IPRIORITYR(gic.rdist_base[cpu], i) = 0x20202020U;    // priority 32
    }
    dprintf( "GICR Init End\n");
}

void gic_icc_init(void)
{
    uint64_t reg;
    dprintf( "ICC Init Start!\n");
    scr_write_ICC_IGRPEN1_EL1(0x0U);

    /* program the idle priority in the PMR */
    scr_write_ICC_PMR_EL1(0xffU);

    /* enables Group 1 interrupts */
    scr_write_ICC_IGRPEN1_EL1(0x1U);

    dprintf( "ICC Init End!\n");
}

void gic_cpu_init(uint64_t base) 
{
    gic_rdist_init(base);
    gic_icc_init();
}

